Popular Searches
Popular Course Categories
Popular Courses

Passing Data Between Screens

Passing Data Between Screens

Flutter Navigation & Screens

Passing Data Between Screens in Flutter

Passing data between screens is a common requirement in Flutter applications. When a user selects an item, opens a profile, edits a product, selects a setting, or completes a form, one screen often needs to send information to another screen or receive a result back.

Flutter screens are widgets, and data can be passed to another screen through constructor parameters, route arguments, named-route arguments, or other application-state patterns. Flutter's official cookbook demonstrates passing objects directly through a screen constructor and also passing objects through RouteSettings or named-route arguments. :contentReference[oaicite:0]{index=0}


1. Why Pass Data Between Screens?

Applications frequently need to transfer information during navigation.

  • Send a product from a product list to a product details screen.
  • Send a user object to a profile screen.
  • Send an article to an article details screen.
  • Send an ID to fetch details from an API.
  • Send form information to a confirmation screen.
  • Send selected settings to another screen.
  • Return a selected value from one screen to another.
  • Return the result of a form or selection screen.

2. Basic Concept

Suppose we have two screens:

Screen A
   |
   | Send Data
   ↓
Screen B

For example:

Product List
     |
     | Product object
     ↓
Product Details

The Product List screen sends the selected product to the Product Details screen.


3. Main Ways to Pass Data

MethodUse Case
Constructor parametersSimple and type-safe direct navigation
RouteSettingsPassing an object through a route
Named route argumentsPassing data with Navigator.pushNamed()
onGenerateRouteCentralized route and argument handling
Navigator.pop() resultReturning data to the previous screen
Shared application stateData needed by multiple unrelated screens
Routing packagesComplex navigation and deep-linking scenarios

4. Passing Data Using Constructor Parameters

The simplest approach is to pass data directly through the destination widget's constructor.

Example

class ProfileScreen extends StatelessWidget {
  final String name;

  const ProfileScreen({
    super.key,
    required this.name,
  });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Profile'),
      ),
      body: Center(
        child: Text(
          'Welcome, $name',
          style: const TextStyle(fontSize: 24),
        ),
      ),
    );
  }
}

Navigate to the screen:

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => const ProfileScreen(
      name: 'Rahul',
    ),
  ),
);

Here, the value Rahul is passed from the first screen to ProfileScreen.


5. Passing Multiple Values

You can pass multiple values through constructor parameters.

class ProfileScreen extends StatelessWidget {
  final String name;
  final int age;
  final String email;

  const ProfileScreen({
    super.key,
    required this.name,
    required this.age,
    required this.email,
  });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(name),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text('Name: $name'),
            Text('Age: $age'),
            Text('Email: $email'),
          ],
        ),
      ),
    );
  }
}

Navigate:

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => const ProfileScreen(
      name: 'Rahul',
      age: 25,
      email: '[email protected]',
    ),
  ),
);

6. Passing a Custom Dart Object

When multiple values belong to the same entity, creating a model class is usually cleaner than passing many individual parameters.

class User {
  final int id;
  final String name;
  final String email;

  const User({
    required this.id,
    required this.name,
    required this.email,
  });
}

Create a user:

const user = User(
  id: 101,
  name: 'Rahul',
  email: '[email protected]',
);

Pass it to another screen:

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => ProfileScreen(
      user: user,
    ),
  ),
);

Destination screen:

class ProfileScreen extends StatelessWidget {
  final User user;

  const ProfileScreen({
    super.key,
    required this.user,
  });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(user.name),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text('ID: ${user.id}'),
            Text('Name: ${user.name}'),
            Text('Email: ${user.email}'),
          ],
        ),
      ),
    );
  }
}

This pattern is particularly useful when the destination screen needs a complete object.


7. Complete Product Example

Flutter's official navigation cookbook demonstrates a similar pattern using a Todo model: the selected object is passed to a details screen through its constructor. :contentReference[oaicite:1]{index=1}

import 'package:flutter/material.dart';

class Product {
  final int id;
  final String name;
  final double price;

  const Product({
    required this.id,
    required this.name,
    required this.price,
  });
}

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    final products = [
      const Product(
        id: 1,
        name: 'Laptop',
        price: 65000,
      ),
      const Product(
        id: 2,
        name: 'Smartphone',
        price: 35000,
      ),
      const Product(
        id: 3,
        name: 'Headphones',
        price: 5000,
      ),
    ];

    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: ProductListScreen(products: products),
    );
  }
}

class ProductListScreen extends StatelessWidget {
  final List products;

  const ProductListScreen({
    super.key,
    required this.products,
  });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Products'),
      ),
      body: ListView.builder(
        itemCount: products.length,
        itemBuilder: (context, index) {
          final product = products[index];

          return ListTile(
            title: Text(product.name),
            subtitle: Text('₹${product.price}'),
            trailing: const Icon(Icons.arrow_forward),
            onTap: () {
              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => ProductDetailsScreen(
                    product: product,
                  ),
                ),
              );
            },
          );
        },
      ),
    );
  }
}

class ProductDetailsScreen extends StatelessWidget {
  final Product product;

  const ProductDetailsScreen({
    super.key,
    required this.product,
  });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(product.name),
      ),
      body: Padding(
        padding: const EdgeInsets.all(20),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              product.name,
              style: const TextStyle(
                fontSize: 28,
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 12),
            Text(
              'Product ID: ${product.id}',
            ),
            const SizedBox(height: 8),
            Text(
              'Price: ₹${product.price}',
              style: const TextStyle(fontSize: 20),
            ),
          ],
        ),
      ),
    );
  }
}

8. Passing Data Using RouteSettings

Another approach is to attach data to a route using RouteSettings(arguments: ...).

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => const ProductDetailsScreen(),
    settings: RouteSettings(
      arguments: product,
    ),
  ),
);

The destination screen can retrieve the object:

final product =
    ModalRoute.of(context)!.settings.arguments as Product;

Flutter's official documentation demonstrates this approach for passing an object to a new screen. :contentReference[oaicite:2]{index=2}


9. Complete RouteSettings Example

class ProductDetailsScreen extends StatelessWidget {
  const ProductDetailsScreen({super.key});

  @override
  Widget build(BuildContext context) {
    final product =
        ModalRoute.of(context)!.settings.arguments as Product;

    return Scaffold(
      appBar: AppBar(
        title: Text(product.name),
      ),
      body: Center(
        child: Text(
          'Price: ₹${product.price}',
          style: const TextStyle(fontSize: 24),
        ),
      ),
    );
  }
}

Navigation:

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => const ProductDetailsScreen(),
    settings: RouteSettings(
      arguments: product,
    ),
  ),
);

10. Passing Data Using Named Routes

When using named routes, data can be passed through the arguments parameter of Navigator.pushNamed().

Navigator.pushNamed(
  context,
  '/profile',
  arguments: user,
);

The destination can retrieve the argument:

final user =
    ModalRoute.of(context)!.settings.arguments as User;

Flutter's official named-route recipe documents this approach. It also notes that named routes are no longer recommended for most new applications, so this technique is particularly useful when working with existing named-route applications or learning the API. :contentReference[oaicite:3]{index=3}


11. Complete Named Route Data Example

import 'package:flutter/material.dart';

class User {
  final String name;
  final String email;

  const User({
    required this.name,
    required this.email,
  });
}

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      routes: {
        '/': (context) => const HomeScreen(),
        '/profile': (context) => const ProfileScreen(),
      },
    );
  }
}

class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    const user = User(
      name: 'Rahul',
      email: '[email protected]',
    );

    return Scaffold(
      appBar: AppBar(
        title: const Text('Home'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            Navigator.pushNamed(
              context,
              '/profile',
              arguments: user,
            );
          },
          child: const Text('Open Profile'),
        ),
      ),
    );
  }
}

class ProfileScreen extends StatelessWidget {
  const ProfileScreen({super.key});

  @override
  Widget build(BuildContext context) {
    final user =
        ModalRoute.of(context)!.settings.arguments as User;

    return Scaffold(
      appBar: AppBar(
        title: const Text('Profile'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(20),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text('Name: ${user.name}'),
            Text('Email: ${user.email}'),
          ],
        ),
      ),
    );
  }
}

12. Passing Multiple Arguments

You can pass multiple values using a custom class.

class ScreenArguments {
  final String title;
  final String message;

  const ScreenArguments({
    required this.title,
    required this.message,
  });
}

Pass the arguments:

Navigator.pushNamed(
  context,
  '/details',
  arguments: const ScreenArguments(
    title: 'Flutter',
    message: 'Welcome to Flutter development!',
  ),
);

Read them:

final args =
    ModalRoute.of(context)!.settings.arguments as ScreenArguments;

Text(args.title);
Text(args.message);

13. Using onGenerateRoute for Data Passing

onGenerateRoute allows route arguments to be processed centrally.

MaterialApp(
  onGenerateRoute: (settings) {
    if (settings.name == '/profile') {
      final user = settings.arguments as User;

      return MaterialPageRoute(
        builder: (context) {
          return ProfileScreen(user: user);
        },
      );
    }

    return MaterialPageRoute(
      builder: (context) => const HomeScreen(),
    );
  },
);

Navigate:

Navigator.pushNamed(
  context,
  '/profile',
  arguments: user,
);

This approach keeps route-specific argument handling in one central location.


14. Returning Data from a Screen

Data does not always move only from the first screen to the second screen. Sometimes the second screen needs to send a result back to the first screen.

Screen A
   |
   | Open Screen B
   ↓
Screen B
   |
   | Return Result
   ↓
Screen A

Flutter's official cookbook uses Navigator.pop(context, result) to return a result from a screen. The original screen can await the Future returned by Navigator.push(). :contentReference[oaicite:4]{index=4}


15. Basic Return Data Example

Open the second screen:

final result = await Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => const SelectionScreen(),
  ),
);

Return data from the second screen:

Navigator.pop(
  context,
  'Selected Item',
);

The returned value is received by the first screen through the result variable.


16. Complete Return Data Example

import 'package:flutter/material.dart';

void main() {
  runApp(
    const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: HomeScreen(),
    ),
  );
}

class HomeScreen extends StatefulWidget {
  const HomeScreen({super.key});

  @override
  State createState() => _HomeScreenState();
}

class _HomeScreenState extends State {
  String selectedValue = 'Nothing selected';

  Future openSelectionScreen() async {
    final result = await Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) => const SelectionScreen(),
      ),
    );

    if (!mounted) return;

    if (result != null) {
      setState(() {
        selectedValue = result;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Return Data Example'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              selectedValue,
              style: const TextStyle(fontSize: 20),
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: openSelectionScreen,
              child: const Text('Choose Option'),
            ),
          ],
        ),
      ),
    );
  }
}

class SelectionScreen extends StatelessWidget {
  const SelectionScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Select Option'),
      ),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          ElevatedButton(
            onPressed: () {
              Navigator.pop(
                context,
                'Option A',
              );
            },
            child: const Text('Option A'),
          ),
          ElevatedButton(
            onPressed: () {
              Navigator.pop(
                context,
                'Option B',
              );
            },
            child: const Text('Option B'),
          ),
        ],
      ),
    );
  }
}

17. Understanding Future in Navigation

When Navigator.push() opens another screen, it returns a Future. That Future completes when the pushed route is popped.

final result = await Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => const SelectionScreen(),
  ),
);

Here:

  • String represents the type of result expected.
  • await waits for the destination screen to close.
  • The returned value is stored in result.

18. Returning an Integer

The returned value does not have to be a String.

final selectedId = await Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => const ProductSelectionScreen(),
  ),
);

Return an integer:

Navigator.pop(
  context,
  101,
);

19. Returning a Boolean

Boolean results are useful for confirmation screens.

final confirmed = await Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => const ConfirmationScreen(),
  ),
);

Return the result:

Navigator.pop(context, true);

Then:

if (confirmed == true) {
  print('User confirmed');
}

20. Returning a Custom Object

You can also return a complete object.

class Address {
  final String city;
  final String pincode;

  const Address({
    required this.city,
    required this.pincode,
  });
}

Receive the object:

final address = await Navigator.push
( context, MaterialPageRoute( builder: (context) => const AddressScreen(), ), );

Return it:

Navigator.pop(
  context,
  const Address(
    city: 'Mumbai',
    pincode: '400001',
  ),
);

21. Passing Data Through a Form

A common practical example is sending form data to a confirmation screen.

class RegistrationData {
  final String name;
  final String email;

  const RegistrationData({
    required this.name,
    required this.email,
  });
}

Navigate to confirmation:

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => ConfirmationScreen(
      data: RegistrationData(
        name: nameController.text,
        email: emailController.text,
      ),
    ),
  ),
);

The confirmation screen receives the object through its constructor.


22. Practical E-Commerce Example

Suppose a product list contains:

Product(
  id: 101,
  name: 'Laptop',
  price: 65000,
)

When the user taps the product:

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => ProductDetailsScreen(
      product: product,
    ),
  ),
);

The details screen displays:

Product Details
----------------------
Name: Laptop
ID: 101
Price: ₹65000
----------------------
[ Add to Cart ]

When the user adds the item to the cart, the details screen can return a result:

Navigator.pop(
  context,
  product,
);

The previous screen can receive the selected product:

final addedProduct = await Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => ProductDetailsScreen(
      product: product,
    ),
  ),
);

23. Passing Data to an Edit Screen

Another common use case is editing an existing object.

class Profile {
  final String name;
  final String email;

  const Profile({
    required this.name,
    required this.email,
  });
}

Open the edit screen:

final updatedProfile = await Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => EditProfileScreen(
      profile: profile,
    ),
  ),
);

After editing:

Navigator.pop(
  context,
  updatedProfile,
);

The original screen can then update its UI with the returned profile.


24. Passing Data Through a ListView

Passing the selected item from a list to a detail screen is one of the most common Flutter navigation patterns.

ListView.builder(
  itemCount: products.length,
  itemBuilder: (context, index) {
    final product = products[index];

    return ListTile(
      title: Text(product.name),
      subtitle: Text('₹${product.price}'),
      onTap: () {
        Navigator.push(
          context,
          MaterialPageRoute(
            builder: (context) => ProductDetailsScreen(
              product: product,
            ),
          ),
        );
      },
    );
  },
)

The official Flutter cookbook uses the same general pattern with a list of objects and a details screen. :contentReference[oaicite:5]{index=5}


25. Passing Only an ID

You do not always need to send the entire object. Sometimes sending an ID is more appropriate.

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => ProductDetailsScreen(
      productId: 101,
    ),
  ),
);

The destination can then use the ID to obtain the latest product information from a repository or API.

class ProductDetailsScreen extends StatelessWidget {
  final int productId;

  const ProductDetailsScreen({
    super.key,
    required this.productId,
  });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Text(
          'Product ID: $productId',
        ),
      ),
    );
  }
}

26. Passing Data with go_router

For applications using go_router, data can be represented through path parameters, query parameters, or extra data depending on the routing requirement.

Path Parameter Example

GoRoute(
  path: '/product/:id',
  builder: (context, state) {
    final id = state.pathParameters['id']!;

    return ProductDetailsScreen(
      productId: id,
    );
  },
)

Navigate to:

context.go('/product/101');

The destination receives 101 as the product ID.


27. Passing Extra Data with go_router

For richer in-memory objects, go_router also supports the extra field.

context.push(
  '/product-details',
  extra: product,
);

Read the object:

GoRoute(
  path: '/product-details',
  builder: (context, state) {
    final product = state.extra as Product;

    return ProductDetailsScreen(
      product: product,
    );
  },
)

When choosing between a URL parameter and an object passed in memory, consider whether the destination should be directly addressable through a URL. URL-based identifiers are particularly useful for deep links.


28. Data Passing and Deep Linking

There is an important difference between passing an object directly and passing information through a URL.

MethodExampleSuitable For
Object through constructorProduct(product: product)Direct in-app navigation
Route argumentarguments: productNamed-route applications
Path parameter/product/101Deep links and URLs
Query parameter?category=laptopFilter/search state
Shared stateProvider/other state solutionData needed across multiple screens

29. Handling Null Results

A user might close a screen without selecting or returning anything. Therefore, returned data should often be treated as nullable.

final result = await Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => const SelectionScreen(),
  ),
);

if (result != null) {
  print('Selected: $result');
} else {
  print('No selection made');
}

30. Checking mounted After await

When a State object awaits navigation and then uses its BuildContext or updates state, it is important to account for the possibility that the widget was removed while waiting.

final result = await Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => const SelectionScreen(),
  ),
);

if (!mounted) return;

setState(() {
  selectedValue = result;
});

Flutter's official returning-data recipe explicitly checks context.mounted after the asynchronous navigation operation before using the context. :contentReference[oaicite:6]{index=6}


31. Data Flow Example

Product List
     |
     | Product object
     ↓
Product Details
     |
     | true / updated product
     ↓
Product List

This represents two-way navigation data flow:

  • The first screen sends data to the second screen.
  • The second screen optionally returns a result.
  • The first screen receives and processes that result.

32. Passing Data Between Three Screens

Suppose we have:

Home
 ↓
Product Details
 ↓
Checkout

Home can pass the product:

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => ProductDetailsScreen(
      product: product,
    ),
  ),
);

Product Details can pass the same product to Checkout:

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => CheckoutScreen(
      product: product,
    ),
  ),
);

33. Passing a List of Objects

Sometimes the destination needs multiple objects.

class CartScreen extends StatelessWidget {
  final List products;

  const CartScreen({
    super.key,
    required this.products,
  });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Cart'),
      ),
      body: ListView.builder(
        itemCount: products.length,
        itemBuilder: (context, index) {
          return ListTile(
            title: Text(products[index].name),
            subtitle: Text(
              '₹${products[index].price}',
            ),
          );
        },
      ),
    );
  }
}

Navigate:

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => CartScreen(
      products: cartProducts,
    ),
  ),
);

34. Passing Data from a Form

class UserFormData {
  final String name;
  final String email;
  final String phone;

  const UserFormData({
    required this.name,
    required this.email,
    required this.phone,
  });
}

Collect the data:

final data = UserFormData(
  name: nameController.text,
  email: emailController.text,
  phone: phoneController.text,
);

Send it to the next screen:

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => ConfirmationScreen(
      data: data,
    ),
  ),
);

35. Returning Form Data

An edit screen can return updated information:

Navigator.pop(
  context,
  updatedUser,
);

The previous screen receives it:

final updatedUser = await Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => EditUserScreen(
      user: user,
    ),
  ),
);

if (!mounted) return;

if (updatedUser != null) {
  setState(() {
    user = updatedUser;
  });
}

36. Constructor vs Route Arguments

AspectConstructorRoute Arguments
Type safetyStrong and explicitRequires casting when using dynamic arguments
ReadabilityVery clearLess direct
Direct navigationExcellentUseful but more indirect
Named routesNot requiredCommonly used
Large objectsEasy to modelPossible with arguments
MaintenanceGenerally straightforwardRequires consistent argument handling

37. Best Practices for Passing Data

  • Use constructor parameters for straightforward direct navigation.
  • Create model classes for related data instead of passing many unrelated parameters.
  • Use typed objects wherever practical.
  • Validate route arguments before casting them.
  • Use IDs when the destination should load fresh data from a repository or API.
  • Use URL/path parameters when information needs to be represented in a deep link.
  • Use Navigator.pop(context, result) when a screen needs to return a result.
  • Handle nullable results when the user can leave without making a selection.
  • Check mounted after asynchronous navigation before calling setState() or using a context-dependent operation.
  • Avoid passing very large or unnecessary objects between many screens.
  • For application-wide shared data, consider an appropriate state-management architecture rather than repeatedly passing the same object through many screens.

38. Common Mistakes

Mistake 1: Forgetting Required Data

class ProfileScreen extends StatelessWidget {
  final User user;

  const ProfileScreen({
    super.key,
    required this.user,
  });
}

Make sure the user object is provided when creating the screen.

Mistake 2: Incorrect Type Casting

final user =
    ModalRoute.of(context)!.settings.arguments as User;

If another type is passed as an argument, the cast can fail. Ensure that the sender and receiver agree on the expected type.

Mistake 3: Forgetting to Return Data

If the previous screen awaits a result:

final result = await Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => const SelectionScreen(),
  ),
);

The destination should return the expected value when appropriate:

Navigator.pop(context, 'Selected');

Mistake 4: Ignoring Null Results

A screen may be closed without returning a result, so handle null when appropriate.

Mistake 5: Updating a Removed Widget

After an asynchronous navigation operation, check mounted before updating a State object.


39. Practical Student Management Example

Consider a student list application.

class Student {
  final int id;
  final String name;
  final String course;

  const Student({
    required this.id,
    required this.name,
    required this.course,
  });
}

Send the selected student:

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => StudentDetailsScreen(
      student: student,
    ),
  ),
);

Display the data:

class StudentDetailsScreen extends StatelessWidget {
  final Student student;

  const StudentDetailsScreen({
    super.key,
    required this.student,
  });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(student.name),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text('ID: ${student.id}'),
            Text('Name: ${student.name}'),
            Text('Course: ${student.course}'),
          ],
        ),
      ),
    );
  }
}

40. Practical Settings Example

A settings screen can return a selected theme mode.

Open Settings:

final selectedTheme = await Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => const SettingsScreen(),
  ),
);

Return the selection:

Navigator.pop(
  context,
  'dark',
);

Handle it:

if (!mounted) return;

if (selectedTheme == 'dark') {
  print('Dark theme selected');
}

41. Practical Selection Example

A country-selection screen can return a selected country.

final country = await Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => const CountryScreen(),
  ),
);

Country screen:

ListTile(
  title: const Text('India'),
  onTap: () {
    Navigator.pop(context, 'India');
  },
)

42. Data Passing Flow

┌──────────────────────┐
│ Screen A             │
│ Product List         │
└──────────┬───────────┘
           │
           │ Product object
           ↓
┌──────────────────────┐
│ Screen B             │
│ Product Details      │
└──────────┬───────────┘
           │
           │ Selected result
           ↓
┌──────────────────────┐
│ Screen A             │
│ Updated UI           │
└──────────────────────┘

43. Choosing the Right Approach

RequirementApproach
Simple direct screen navigationConstructor parameters
Product list to product detailsPass model object through constructor
Existing named-route applicationNamed route arguments
Central route processingonGenerateRoute
Return selected valueNavigator.pop(context, result)
Complex shared application stateState-management architecture
Deep-linkable product pageURL/path parameter
Complex routing/deep linkingRouter-based solution such as go_router

44. Interview Questions

Q1. How can you pass data between Flutter screens?

Data can be passed through constructor parameters, route arguments, RouteSettings, named-route arguments, or a suitable application-state architecture.

Q2. What is the simplest way to pass data to another screen?

For direct navigation, passing data through the destination widget's constructor is a straightforward approach.

Q3. How do you pass data using named routes?

Use the arguments parameter of Navigator.pushNamed().

Q4. How do you retrieve named-route arguments?

Use ModalRoute.of(context)!.settings.arguments or process the arguments in onGenerateRoute().

Q5. How do you return data to the previous screen?

Use Navigator.pop(context, result).

Q6. What does Navigator.push return?

It returns a Future that completes when the pushed route is popped, optionally containing the returned result.

Q7. Why should you check mounted after await?

The widget may have been removed while waiting for the navigation Future. Checking mounted prevents updating or using a context from a widget that is no longer active.

Q8. Should you always pass the entire object to another screen?

No. Depending on the application, passing an ID and loading the latest data can be more appropriate, especially when the destination needs fresh data or the route needs to be deep-linkable.

Q9. Can a custom object be passed between screens?

Yes. Dart objects can be passed through constructors and can also be passed through route arguments.


45. Practice Exercise

Create a Flutter application with the following screens:

  • Home Screen
  • Product List Screen
  • Product Details Screen
  • Cart Screen
  • Edit Product Screen
  • Confirmation Screen

Implement the following:

  1. Create a Product model.
  2. Display multiple products using ListView.builder.
  3. Pass the selected product to the Product Details screen.
  4. Display the product's name, ID, price, and description.
  5. Pass the product to the Edit Product screen.
  6. Return the updated product using Navigator.pop().
  7. Display the updated product on the previous screen.
  8. Pass a list of products to the Cart screen.
  9. Return a Boolean confirmation result from the Confirmation screen.
  10. Handle null results and check mounted after asynchronous navigation.

46. Quick Revision

ConceptCodePurpose
Constructor dataScreen(data: value)Directly pass data to a widget
Push screenNavigator.push()Open another screen
Route argumentsRouteSettings(arguments: value)Attach data to a route
Named route argumentsarguments: valuePass data with pushNamed()
Read argumentsModalRoute.of(context)Retrieve route data
Dynamic argumentsonGenerateRouteProcess arguments centrally
Return resultNavigator.pop(context, result)Send data back
Receive resultawait Navigator.push()Wait for returned data
Check widget statemountedEnsure State is still active
URL parameter/product/:idPass data through a route path

47. Key Takeaways

  • Passing data between screens is a fundamental Flutter navigation concept.
  • Constructor parameters provide a clear way to pass data during direct navigation.
  • Custom model classes are useful for passing structured data.
  • RouteSettings can attach arguments to a route.
  • Named routes can receive data through the arguments parameter.
  • ModalRoute.of(context) can retrieve route arguments.
  • onGenerateRoute can centralize route and argument handling.
  • Navigator.pop(context, result) can return data to the previous screen.
  • Navigator.push() returns a Future that can be awaited for a result.
  • Always consider nullable results when the user can leave a screen without selecting anything.
  • Check mounted after asynchronous navigation before updating widget state.
  • For deep-linkable routes, URL/path parameters can be more appropriate than passing only an in-memory object.
  • For complex navigation requirements, a modern Router-based solution such as go_router can be considered.

48. Official Flutter Resources


49. Learn Flutter with JustAcademy

To learn Flutter development, Dart programming, widgets, navigation, state management, APIs, Firebase integration, UI development, and practical Flutter projects, explore these resources:

JustAcademy Flutter Training Course

Register for Flutter Course Demo

whatsapp